package com.jd.jstable.strategy;

import com.alibaba.fastjson.JSON;
import com.jd.jstable.config.KeyMode;
import com.jd.jstable.utils.MD5Util;
import com.jd.jstable.utils.ReflectionUtil;
import com.jd.jstable.utils.SpringContextHolder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 策略工具包
 * Created by chenyipu on 2018/1/10.
 */
public class StrategyUtil {

    private static final Log logger = LogFactory.getLog(StrategyUtil.class);

//    private static String makeResourceKey(Class<?> aClass, String methodName, Object[] args, KeyMode keyMode) {
//        if (Objects.equals(keyMode.getValue(), KeyMode.SOME_ARG)){
//            return makeArgKey(aClass, methodName, args, keyMode);
//        }
//        List<Object> argList = Arrays.stream(args).collect(Collectors.toList());
//        ResourceKey resourceKey = new ResourceKey(aClass.getName(), methodName, argList);
//        String key = JSON.toJSONString(resourceKey);
//        return key;
//    }

    public static String makeResourceKey(Class<?> aClass, String methodName, Object[] args, KeyMode keyMode) {
        if (Objects.equals(keyMode.getValue(), KeyMode.SOME_ARG)){
            return makeArgKey(aClass, methodName, args, keyMode);
        }
        List<Object> argList = Arrays.stream(args).collect(Collectors.toList());
        ResourceKey resourceKey = new ResourceKey(aClass.getName(), methodName, argList);
        String key = resourceKey.toEasyString();
        return key;
    }

    public static String makeResourceKeyMd5(Class<?> aClass, String methodName, Object[] args, KeyMode keyMode) {
        if (Objects.equals(keyMode.getValue(), KeyMode.SOME_ARG)){
            return makeArgKey(aClass, methodName, args, keyMode);
        }
        return MD5Util.MD5(makeResourceKey(aClass, methodName, args, keyMode));
    }

    public static String makeArgKey(Class<?> aClass, String methodName, Object[] args, KeyMode keyMode) {
        if (keyMode.getValue().equals(KeyMode.SOME_ARG)) {
            if(keyMode.getIndex() != null && !Objects.equals(keyMode.getIndex(), 0)){
                if(keyMode.getIndex() >= args.length) {
                    throw new ArrayIndexOutOfBoundsException();
                }
            }
            if (keyMode.getIndex() == null) {
                throw new ArrayIndexOutOfBoundsException();
            }
            if(Objects.equals(0, keyMode.getIndex()) && args.length==0){
                return null;
            }
            return args[keyMode.getIndex()] == null ? "null" : args[keyMode.getIndex()].toString();
        }
        throw new RuntimeException("can not reached here!");
    }

    public static List<Pair> extractStrategies(String strategies) {
        return Arrays.stream(strategies.split(";|,")).map(strategy -> {
            String[] pair = strategy.split(":");
            String param = null;
            if (pair.length == 2) {
                param = pair[1];
            }
            return new Pair(getStrategy(pair[0]), param);
        }).collect(Collectors.toList());
    }

    public static Strategy getStrategy(String strategy) {

        Strategy strategyResult = null;
        try {
            strategyResult = (Strategy) SpringContextHolder.getBean(String.format("MD5_138cc4666fbd6262a0a57cab833c5dbc%s", strategy));
        } catch (Exception e) {

        }
        if (strategyResult == null) {
            strategyResult = UnknownStrategy.getInstance();
        }
        return strategyResult;
    }

    public static Object solve(List<Pair> list, Object invoker, Method method, Object[] args, KeyMode keyMode) {
        for (Pair pair : list) {
            Object result = null;
            try {
                if (!(pair.getStrategy() instanceof UnknownStrategy)) {
                    result = pair.getStrategy().solve(invoker, method, args, pair.getParam(), keyMode);
                }
            } catch (Exception exp) {

                ////send exception stacktrace
                logger.error(String.format("Strategy [%s] Solve Failed.", pair.getStrategy().getClass()), exp);
            }
            if (result != null) {
                return result;
            }
        }
        return null;
    }

    public static void feed(List<Pair> list,  Object invoker, Method method, Object[] args, KeyMode keyMode, Object result){
        List<Pair> listFiltered = list.stream().filter(pair -> ReflectionUtil.hasExtends(pair.getStrategy().getClass(), ResultOrientedStrategy.class)).collect(Collectors.toList());
        for (int i = 0; i < listFiltered.size(); i++) {
            Pair pair = listFiltered.get(i);
            try {
                long start2 = System.currentTimeMillis();
                ((ResultOrientedStrategy) pair.getStrategy()).feed(invoker, method, args, result, pair.getParam(), keyMode);
                logger.info("Feed Time:" + pair.getStrategy().getClass() + ":" + (System.currentTimeMillis() - start2));
            }catch (Exception feedE){
                logger.error(String.format("Strategy [%s] Feed Failed.", pair.getStrategy().getClass()), feedE);
            }
        }
    }

    public static void reset(List<Pair> list, Object invoker, Method method, Object[] args, KeyMode keyMode){
        List<Pair> listFiltered = list.stream().filter(pair -> ReflectionUtil.hasImplemented(pair.getStrategy().getClass(), ResetableStrategy.class)).collect(Collectors.toList());
        for (int i = 0; i < listFiltered.size(); i++) {
            Pair pair = listFiltered.get(i);
            try {
                long start2 = System.currentTimeMillis();
                ((ResetableStrategy) pair.getStrategy()).reset(invoker, method, args, pair.getParam(), keyMode);
                logger.info("Reset Time:" + pair.getStrategy().getClass() + ":" + (System.currentTimeMillis() - start2));
            }catch (Exception e){
                logger.error(String.format("Strategy [%s] Reset Failed.", pair.getStrategy().getClass()), e);
            }
        }
    }
}
